﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Asistente.Managers;
using System.Collections.ObjectModel;
using Asistente.Model.IFPUG;
using Asistente.Model.General;
using Asistente.Model.IFPUG.MeasureUtilities;

namespace Asistente.Model.Representation
{
	public class UseCase : Element, IElement
	{
		// Ifpug
		public int Dets { get; set; }
		public int Ftrs { get; set; }
		public TransactionalFunctionType Type { get; set; }
		public bool OverwrittenMeasuresIfpug { get; set; }

		// Cosmic
		public int Entries { get; set; }
		public int Exits { get; set; }
		public int Reads { get; set; }
		public int Writes { get; set; }
		public int CosmicPoints { get; set;}
		public bool OverwrittenMeasuresCosmic { get; set; }
		
		public string Name { get; set; }
		public string Description { get; set; }
		public bool DisplayMessage { get; set; }
		public bool Auditable { get; set; }
		public UseCasePattern Pattern { get; set; }
		
		private Collection<Screen> screens;
		public Collection<Screen> Screens
		{
			get { return screens; }
			set { screens = value; } 
		}
		
		public UseCaseLayout UseCaseLayout { get; set; }
		public UseCaseCardinality UseCaseCardinaity { get; set; }
		public UseCaseComplexity UseCaseComplexity { get; set; }
		public UseCaseLayoutCombinations UseCaseLayoutCombinations { get; set; }

		internal UseCase()
		{
			screens = new Collection<Screen>();
			Name = Numerator.Instance.GetName(GetType());
		}

		public void DeleteScreen(Screen screen) 
		{
			if (Screens.Contains(screen))
				Screens.Remove(screen);
		}

		public void DeletePanel(Panel panel)
		{
			foreach (Screen screen in Screens)
			{
				screen.DeletePanel(panel);
			}            
		}

		public Screen NewScreen()
		{
			Screen screen = new Screen();
			screen.Name = Numerator.Instance.GetName(screen.GetType());
			screen.Description = screen.Name;
			this.Screens.Add(screen);
			return screen;
		}

		#region IfpugModel Converter
		
        //Un caso de uso se convierte generando un Transformed Use CAse
        //Se debe identificar el tipo de funcion de transaccion que se genera
        //Tambien los dets que se aportan segun los controles y los FTR segun
        //las entidades referenciadas.

		public IMeasurable ConvertToIfpugModel(IfpugModel ifpugModel)
		{
			//Se crea el TransformedUseCase
			TransformedUseCase transformedUseCase = new TransformedUseCase{UseCase = this};

            //Si se ha sobreescribio la medida, se toma esa, en otro caos calculamos todos.
			if (OverwrittenMeasuresIfpug)
			{
				transformedUseCase.TransactionalFunction.Type = Type;
			}
			else
			{
				transformedUseCase.TransactionalFunction.Type = Identifier.Instance.Identify(this);
			}

			//Se asignan DET y FTR
			transformedUseCase.AddDet(GetDets());
			transformedUseCase.AddFtr(GetFtrs());
			
			ifpugModel.Add(transformedUseCase);					 
			return ifpugModel;            
		}

		//Se retornan los FTR que aporta este caso de uso
		private int GetFtrs()
		{
			if (OverwrittenMeasuresIfpug)
			{
				return Ftrs;
			}

			List<Entity> entities = new List<Entity>();
			
			//Vamos a agregar las entidades de todos los paneles, ya sean editables o no editables
			//pero sin agregar entidades repetidas para no contarlas varias veces. Tambien se agregan
			//las entidades referenciadas de los master details y tambien las entidades 
			//referenciadas por los controles que agregan FTR.
			foreach (Screen screen in Screens)
			{
				//Agregamos las entidades de los paneles
				foreach (Panel panel in screen.Panels)
				{
					if (typeof(EditablePanel).IsAssignableFrom(panel.GetType()))
					{
						//Es EditablePanel
						AddIfNotContains(entities, (GetEntitiesEditablePabel(panel as EditablePanel)));
					}
					else 
					{
						//Es ReadOnlyPanel
						AddIfNotContains(entities, (GetEntitiesReadOnlyPanel(panel as ReadOnlyPanel)));
					}
				}

				//Agregamos las entidades de los MasterDetail
				foreach (MasterDetail masterDetail in screen.MasterDetails)
				{
					//Master, sabemos que es solamente editable panel              
                    if (masterDetail.Master != null) 
                    { 
                        AddIfNotContains(entities,  (GetEntitiesEditablePabel(masterDetail.Master as EditablePanel)));
                    }
					
					foreach (EditablePanel detail in masterDetail.Details)
					{
						//Es EditablePanel
						AddIfNotContains(entities, (GetEntitiesEditablePabel(detail as EditablePanel)));					    
					}                    
				}

				//Agregamos las entidades de los controles
				foreach (Control control in GetControls())
				{
					AddIfNotContains(entities, (GetEntitiesControl(control)));
				}
			}				

			return entities.Count; //Retornamos la cantidad de entidades distintas referenciadas por el proceso
		}

		//Se retornan los DET que aporta este caso de uso
		private int GetDets()
		{
			if (OverwrittenMeasuresIfpug)
			{
				return Dets;
			}

			int dets = 0;

			//Identificamos los DET por su nombre, para evitar contar repetidos en el proceso transaccional
			List<String> countedControls = new List<String>();

			//Se suman los DET que aporta cada uno de los controles
			foreach (Control control in GetControls())
			{
				if (!countedControls.Contains(control.Name))  // Si se ha contado un DET con este nombre, se lo cuenta y agrega a la lista
				{ 
					dets += ControlManager.Instance.GetDetTransactionalFunction(control.ControlPattern) * control.Quantity;
					countedControls.Add(control.Name);
				}
			}

			//Auditable
			if (Auditable)
			{
				//En caso de ser auditable agrega tres DET, usuario, accion, fecha
				dets += 3;
			}

			//DispaysMessage
			if (DisplayMessage)
			{
				//En caso de desplegar un mensaje aporta un DET
				dets++;
			}

			//LogicalDelete
			if (Pattern == UseCasePattern.LogicDelete)
			{
				//En caso de ser una elimiacion logica aporta un DET
				dets++;
			}

			//Actions
			foreach (Screen screen in Screens)
			{
				//Sumamos las acciones de cada screen
				dets += screen.Actions.Count;
			}

			//Graphs
			foreach (Graph graph in GetGraphs())
			{
				//Por cada grafica aportan:
				//Los det de numero de parametros (corresponden con los campos de una tabla)
				dets += graph.Parameters;
			}

			return dets;
		}

		//Se retornan todas las graphs del useCase
		private IEnumerable<Graph> GetGraphs()
		{
			return (from screen in Screens from panel in screen.Panels where panel.GetType() == typeof (Graph) select panel as Graph).ToList();
		}

		//Agrega elementos a de la segunda lista a la primera, si no estan en la primera
		private void AddIfNotContains(List<Entity> entities, IEnumerable<Entity> list)
		{
			foreach (Entity entity in list)
			{
				//Si no contiene una entidad con el mismo nombre se agrega, no se verifica si est'a la misma entidad (mediante identidad)
				//debido a un bug encontrado cuando se desserializa, se crean multiples instancias para una entidad entonces se cuentan
				//como varios FTR, por lo tanto verificamos si se ha contado mediante el nombre
				if (!Contains(entities,entity.Name)) 
				{
					entities.Add(entity);
				}
			}
		}

		//Devuelve un booleano que indica si existe en entities una entidad con nombre entityName 
		private bool Contains(IEnumerable<Entity> entities, String entityName)
		{
			return entities.Any(entity => entity.Name.Equals(entityName));
		}

		//Se retorna la lista de entidades referenciadas por un readOnlyPanel
		private List<Entity> GetEntitiesReadOnlyPanel(ReadOnlyPanel readOnlyPanel)
		{
			List<Entity> entities = new List<Entity>();

			if (readOnlyPanel.DataGroup != null)
			{
				if (readOnlyPanel.DataGroup.GetType() == typeof(Query))
				{
					// El data group es un query
					Query query = readOnlyPanel.DataGroup as Query;
					if (query != null) entities.AddRange(query.UsedEntities);
				}
				else
				{
					//El data group es una entidad
					entities.Add(readOnlyPanel.DataGroup as Entity);
				}
			}
			return entities;
		}

		//Se retorna la lista de entidades referenciadas por un control
		private List<Entity> GetEntitiesControl(Control control)
		{
			List<Entity> entities = new List<Entity>();

			DataGroup dataGroup = control.DataGroup;

			if(dataGroup!= null)
			{
				if (dataGroup.GetType() == typeof(Query))
				{
					// El data group es un query
					Query query = dataGroup as Query;
					if (query != null) entities.AddRange(query.UsedEntities);
				}
				else
				{
					//El data group es una entidad
					entities.Add(dataGroup as Entity);
				}
			}
			return entities;
		}

		//Se retorna la lista de entidades referenciadas por un editablePanel
		private List<Entity> GetEntitiesEditablePabel(EditablePanel panel)
		{
			List<Entity> entities = new List<Entity>();

			//Agregamos la entidad editada
			if (panel.Entity != null)
			{
				entities.Add(panel.Entity);
			}

			//Agregamos las entidades referenciadas
			if (panel.UpdatesDeletesCascade != null)
			{
				entities.AddRange(panel.UpdatesDeletesCascade);
			}

			return entities;
		}

		//Se retorna la lista de controles de cada uno de los paneles para todos los screen del caso de uso
		private IEnumerable<Control> GetControls()
		{
			List<Control> controls = new List<Control>();

			foreach (Screen screen in Screens)
			{
				//Se agregan todos los controles de los paneles
				foreach (Panel panel in screen.Panels)
				{
					controls.AddRange(panel.Controls);
				}

				//Se agregan los controles de los master details
				foreach (MasterDetail masterDetail in screen.MasterDetails)
				{
					//Controles del master
                    if (masterDetail.Master != null) 
                    { 
					    controls.AddRange(masterDetail.Master.Controls);
                    }

					//Controles de details
					foreach (EditablePanel detail in masterDetail.Details)
					{
						controls.AddRange(detail.Controls);
					}
				}
			}
			return controls;
		}
		
		#endregion

        public UseCase Clone()
        {
            UseCase copy = (UseCase)this.MemberwiseClone();  
            Collection<Screen> screens = new Collection<Screen>();

            foreach (var screen in Screens)
            {
                screens.Add(screen.Clone());
            }

            copy.Screens = screens;
            return copy;
        }

	    public override string ToString()
		{
			StringBuilder content = new StringBuilder();
			content.AppendLine("Name: " + Name);

			if (OverwrittenMeasuresIfpug)
			{
				content.AppendLine("Has OverwrittenMeasuresIfpug enable");
				content.AppendLine("DET: " + Dets);
				content.AppendLine("RET: " + Ftrs);
			}

			return content.ToString();
		}

		internal void NewAction(UseCase useCase)
		{
			if (Screens.Count != 0) 
			{
				//Se asume que la pantalla 0 es la de navegacion
				Screens[0].NewUseCaseAction(useCase);
			}
		}
	}
}
